#!/usr/bin/env bash

if [[ "$OSTYPE" == darwin* && -z "$SWITCHED_TO_ZSH" && "$(ps -p $$ -o comm=)" != "zsh" ]]; then
	export SWITCHED_TO_ZSH=1
	exec env zsh "$0" "$@"
fi

if [[ -n "$BASH_SOURCE" ]]; then
	script_path="${BASH_SOURCE[0]}"
elif [[ -n "$ZSH_VERSION" ]]; then
	script_path="${(%):-%x}"
else
	script_path="$0"
fi

export SCRIPT_DIR="$(cd "$(dirname "$script_path")" >/dev/null 2>&1 && pwd -P)"
export PYTHONUTF8="1"
export PYTHONIOENCODING="utf-8"
export TTS_CACHE="$SCRIPT_DIR/models"
export TESSDATA_PREFIX="$SCRIPT_DIR/models/tessdata"
export TMPDIR="$SCRIPT_DIR/tmp"
export CONDA_HOME="$HOME/Miniforge3"
export CONDA_BIN_PATH="$CONDA_HOME/bin"
export CONDA_ENV="$CONDA_HOME/etc/profile.d/conda.sh"
export PATH="$CONDA_BIN_PATH:$PATH"

NATIVE="native"
BUILD_DOCKER="build_docker"
ARCH=$(uname -m)
PYTHON_VERSION=$(python3 -c 'import sys; print(f"{sys.version_info.major}.{sys.version_info.minor}")' 2>/dev/null || echo "3.12")
MIN_PYTHON_VERSION="3.10"
MAX_PYTHON_VERSION="3.12"
PYTHON_ENV="python_env"
SCRIPT_MODE="$NATIVE"
APP_NAME="ebook2audiobook"
APP_VERSION=$(<"$SCRIPT_DIR/VERSION.txt")
OS_LANG=$(echo "${LANG:-en}" | cut -d_ -f1 | tr '[:upper:]' '[:lower:]')
HOST_PROGRAMS=("curl" "pkg-config" "calibre" "ffmpeg" "nodejs" "espeak-ng" "rust" "sox" "tesseract")
DOCKER_PROGRAMS=("curl" "ffmpeg" "nodejs" "espeak-ng" "sox" "tesseract-ocr")
DOCKER_DEVICE_STR=""
DOCKER_IMG_NAME="$APP_NAME"
CALIBRE_INSTALLER_URL="https://download.calibre-ebook.com/linux-installer.sh"
BREW_INSTALLER_URL="https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh"
MINIFORGE_MACOSX_INSTALLER_URL="https://github.com/conda-forge/miniforge/releases/latest/download/Miniforge3-MacOSX-$(uname -m).sh"
MINIFORGE_LINUX_INSTALLER_URL="https://github.com/conda-forge/miniforge/releases/latest/download/Miniforge3-$(uname)-$(uname -m).sh"
RUST_INSTALLER_URL="https://sh.rustup.rs"
INSTALLED_LOG="$SCRIPT_DIR/.installed"
UNINSTALLER="$SCRIPT_DIR/uninstall.sh"
WGET=$(which wget 2>/dev/null)

typeset -A arguments # associative array
typeset -a programs_missing # indexed array

ARGS=("$@")

# Parse arguments
while (( $# > 0 )); do
    case "$1" in
        --*)
            key="${1#--}"
            if [[ -n "$2" && "$2" != --* ]]; then
                arguments[$key]="$2"
                shift 2
                continue
            else
                arguments[$key]=true
            fi
            ;;
        *)
            echo "Unknown option: $1"
            exit 1
            ;;
    esac
    shift
done

if [[ "${arguments[script_mode]}" != "" ]]; then
	if [[ "${arguments[script_mode]}" == "$BUILD_DOCKER" ]]; then
		SCRIPT_MODE="${arguments[script_mode]}"
	else
		echo "Error: Invalid script mode argument: $SCRIPT_MODE"
		exit 1
	fi
fi

if [[ -n "${arguments[docker_device]+exists}" ]]; then
	DOCKER_DEVICE_STR="${arguments[docker_device]}"
fi

[[ "$OSTYPE" != darwin* && "$SCRIPT_MODE" != "$BUILD_DOCKER" ]] && SUDO="sudo" || SUDO=""
[[ $OSTYPE == darwin* ]] && SHELL_NAME="zsh" || SHELL_NAME="bash"

cd "$SCRIPT_DIR"

if [[ ! -f "$INSTALLED_LOG" ]]; then
	touch "$INSTALLED_LOG"
fi

############### FUNCTIONS ##############

###### DESKTOP APP
function has_no_display {
	if [[ "$OSTYPE" == darwin* ]]; then
		if pgrep -x WindowServer >/dev/null 2>&1 &&
		   [[ "$(launchctl managername 2>/dev/null)" == "Aqua" ]]; then
			return 0   # macOS GUI
		else
			return 1   # SSH or console mode
		fi
	else
		if [[ -n "$SSH_CONNECTION" || -n "$SSH_CLIENT" || -n "$SSH_TTY" ]]; then
			return 1
		fi

		if [[ -z "$DISPLAY" && -z "$WAYLAND_DISPLAY" ]]; then
			return 1   # No display server → headless
		fi

		if pgrep -x vncserver	>/dev/null 2>&1 || \
		   pgrep -x Xvnc		 >/dev/null 2>&1 || \
		   pgrep -x x11vnc	   >/dev/null 2>&1 || \
		   pgrep -x Xtightvnc	>/dev/null 2>&1 || \
		   pgrep -x Xtigervnc	>/dev/null 2>&1 || \
		   pgrep -x Xrealvnc	 >/dev/null 2>&1; then
			return 0
		fi

		if pgrep -x gnome-shell	   >/dev/null 2>&1 || \
		   pgrep -x plasmashell	   >/dev/null 2>&1 || \
		   pgrep -x xfce4-session	 >/dev/null 2>&1 || \
		   pgrep -x cinnamon		  >/dev/null 2>&1 || \
		   pgrep -x mate-session	  >/dev/null 2>&1 || \
		   pgrep -x lxsession		 >/dev/null 2>&1 || \
		   pgrep -x openbox		   >/dev/null 2>&1 || \
		   pgrep -x i3				>/dev/null 2>&1 || \
		   pgrep -x sway			  >/dev/null 2>&1 || \
		   pgrep -x hyprland		  >/dev/null 2>&1 || \
		   pgrep -x wayfire		   >/dev/null 2>&1 || \
		   pgrep -x river			 >/dev/null 2>&1 || \
		   pgrep -x fluxbox		   >/dev/null 2>&1; then
			return 0   # Desktop environment detected
		fi
		return 1
	fi
}

function open_desktop_app {
	(
		host=127.0.0.1
		port=7860
		url="http://$host:$port/"
		timeout=30
		start_time=$(date +%s)

		while ! nc -z "$host" "$port" >/dev/null 2>&1; do
			sleep 1
			elapsed=$(( $(date +%s) - start_time ))
			if [[ "$elapsed" -ge "$timeout" ]]; then
				exit 0
			fi
		done

		if [[ "$OSTYPE" == darwin* ]]; then
			open "$url" >/dev/null 2>&1 &
		elif command -v xdg-open >/dev/null 2>&1; then
			xdg-open "$url" >/dev/null 2>&1 &
		elif command -v gio >/dev/null 2>&1; then
			gio open "$url" >/dev/null 2>&1 &
		elif command -v x-www-browser >/dev/null 2>&1; then
			x-www-browser "$url" >/dev/null 2>&1 &
		else
			echo "No method found to open the default web browser." >&2
		fi
		exit 0
	) &
}

function mac_app {
	local APP_BUNDLE="$HOME/Applications/$APP_NAME.app"
	local CONTENTS="$APP_BUNDLE/Contents"
	local MACOS="$CONTENTS/MacOS"
	local RESOURCES="$CONTENTS/Resources"
	local DESKTOP_DIR="$(osascript -e 'POSIX path of (path to desktop folder)' 2>/dev/null | sed 's:/$::')"
	local DESKTOP_SHORTCUT="$DESKTOP_DIR/$APP_NAME"
	local ICON_PATH="$SCRIPT_DIR/tools/icons/mac/appIcon.icns"
	local OPEN_DESKTOP_APP_DEF=$(typeset -f open_desktop_app)
	local ESCAPED_APP_ROOT=$(printf '%q' "$SCRIPT_DIR") # Escape SCRIPT_DIR safely for AppleScript
	if [[ -d "$APP_BUNDLE" ]]; then
		open_desktop_app
		return 0
	fi
	[[ -d "$HOME/Applications" ]] || mkdir "$HOME/Applications"
	if [[ ! -d "$MACOS" || ! -d "$RESOURCES" ]]; then
		mkdir -p "$MACOS" "$RESOURCES"
	fi
	cat > "$MACOS/$APP_NAME" << EOF
#!/bin/zsh

$OPEN_DESKTOP_APP_DEF

open_desktop_app

# TODO: replace osascript when log will be available in gradio with
#
# cd "$SCRIPT_DIR"
# ./ebook2audiobook.sh

osascript -e '
tell application "Terminal"
do script "cd \"${ESCAPED_APP_ROOT}\" && ./ebook2audiobook.sh"
activate
end tell
'
EOF
	chmod +x "$MACOS/$APP_NAME"
	cp "$ICON_PATH" "$RESOURCES/AppIcon.icns"
	cat > "$CONTENTS/Info.plist" << 'PLIST'
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleExecutable</key>
<string>ebook2audiobook</string>
<key>CFBundleIdentifier</key>
<string>com.local.ebook2audiobook</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>ebook2audiobook</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleVersion</key>
<string>1</string>
<key>LSMinimumSystemVersion</key>
<string>10.9</string>
<key>NSPrincipalClass</key>
<string>NSApplication</string>
<key>CFBundleIconFile</key>
<string>AppIcon</string>
</dict>
</plist>
PLIST
	ln -sf "$APP_BUNDLE" "$DESKTOP_SHORTCUT"
	echo -e "Next launch in GUI mode you just need to double click on the desktop shortcut or go to the launchpad and click on ebook2audiobook icon."
	open_desktop_app
}

function linux_app() {
	local MENU_ENTRY="$HOME/.local/share/applications/$APP_NAME.desktop"
	local DESKTOP_DIR="$(xdg-user-dir DESKTOP 2>/dev/null || echo "$HOME/Desktop")"
	local DESKTOP_SHORTCUT="$DESKTOP_DIR/$APP_NAME.desktop"
	local ICON_PATH="$SCRIPT_DIR/tools/icons/linux/appIcon"
	if [[ -f "$MENU_ENTRY" ]]; then
		open_desktop_app
		return 0
	fi
	mkdir -p "$HOME/.local/share/applications"
	cat > "$MENU_ENTRY" <<EOF
[Desktop Entry]
Type=Application
Name=ebook2audiobook
Exec=$SCRIPT_DIR/ebook2audiobook.sh
Icon=$ICON_PATH
Terminal=true
Categories=Utility;
EOF
	chmod +x "$MENU_ENTRY"
	mkdir -p "$HOME/Desktop" 2>&1 > /dev/null
	cp "$MENU_ENTRY" "$DESKTOP_SHORTCUT"
	chmod +x "$DESKTOP_SHORTCUT"
	if command -v update-desktop-database >/dev/null 2>&1; then
		update-desktop-database ~/.local/share/applications >/dev/null 2>&1
	fi
	echo -e "Next launch in GUI mode you just need to double click on the desktop shortcut or go to menu entry and click on ebook2audiobook icon."
	open_desktop_app
}

function check_desktop_app {
	if [[ " ${ARGS[*]} " == *" --headless "* || has_no_display -eq 1 ]]; then
		return 0
	fi
	if [[ "$OSTYPE" == darwin* ]]; then
		mac_app
	elif [[ "$OSTYPE" == "linux"* ]]; then
		linux_app
	fi
	return 0
}
#################

function get_iso3_lang() {
	case "$1" in
		en) echo "eng" ;;
		fr) echo "fra" ;;
		de) echo "deu" ;;
		it) echo "ita" ;;
		es) echo "spa" ;;
		pt) echo "por" ;;
		ar) echo "ara" ;;
		tr) echo "tur" ;;
		ru) echo "rus" ;;
		bn) echo "ben" ;;
		zh) echo "chi_sim" ;;
		fa) echo "fas" ;;
		hi) echo "hin" ;;
		hu) echo "hun" ;;
		id) echo "ind" ;;
		jv) echo "jav" ;;
		ja) echo "jpn" ;;
		ko) echo "kor" ;;
		pl) echo "pol" ;;
		ta) echo "tam" ;;
		te) echo "tel" ;;
		yo) echo "yor" ;;
		*)  echo "eng" ;;
	esac
}

function check_required_programs {
	local programs=("$@")
	programs_missing=()
	for program in "${programs[@]}"; do
		local pkg="$program"
		local bin="$program"
		# Normalize special binaries
		[[ "$program" == "nodejs" ]] && bin="node"
		[[ "$program" == "rust" ]]   && bin="rustc"
		# Special case: tesseract OCR
		if [[ "$program" == "tesseract" || "$program" == "tesseract-ocr" ]]; then
			bin="tesseract"
			if command -v zypper >/dev/null 2>&1 || command -v apt-get >/dev/null 2>&1 || command -v apk >/dev/null 2>&1; then
				pkg="tesseract-ocr"
			else
				pkg="$program"
			fi
		fi
		if ! command -v "$bin" &>/dev/null; then
			echo -e "\e[33m$pkg is not installed.\e[0m"
			programs_missing+=("$pkg")
		fi
	done
	(( ${#programs_missing[@]} == 0 ))
}

function install_programs {
	if [[ "$OSTYPE" == darwin* ]]; then
		echo -e "\e[33mInstalling required programs...\e[0m"
		PACK_MGR="brew install"
			if ! command -v brew &> /dev/null; then
				echo -e "\e[33mHomebrew is not installed. Installing Homebrew...\e[0m"
				/usr/bin/env bash -c "$(curl -fsSL $BREW_INSTALLER_URL)"
				echo >> $HOME/.zprofile
				echo 'eval "$(/usr/local/bin/brew shellenv)"' >> $HOME/.zprofile
				eval "$(/usr/local/bin/brew shellenv)"
				if ! grep -iqFx "homebrew" "$INSTALLED_LOG"; then
					echo "homebrew" >> "$INSTALLED_LOG"
				fi
			fi
	else
		if [[ "$SUDO" == "sudo" ]]; then
			echo -e "\e[33mInstalling required programs. NOTE: you must have 'sudo' priviliges to install ebook2audiobook.\e[0m"
		fi
		local PACK_MGR_OPTIONS=""
		if command -v emerge &> /dev/null; then
			PACK_MGR="emerge"
		elif command -v dnf &> /dev/null; then
			PACK_MGR="dnf install"
			PACK_MGR_OPTIONS="-y"
		elif command -v yum &> /dev/null; then
			PACK_MGR="yum install"
			PACK_MGR_OPTIONS="-y"
		elif command -v zypper &> /dev/null; then
			PACK_MGR="zypper install"
			PACK_MGR_OPTIONS="-y"
		elif command -v pacman &> /dev/null; then
			PACK_MGR="pacman -Sy --noconfirm"
		elif command -v apt-get &> /dev/null; then
			$SUDO apt-get update
			PACK_MGR="apt-get install"
			PACK_MGR_OPTIONS="-y"
		elif command -v apk &> /dev/null; then
			PACK_MGR="apk add"
		else
			echo "Cannot recognize your applications package manager. Please install the required applications manually."
			return 1
		fi
	fi
	if [[ -z "$WGET" ]]; then
		echo -e "\e[33m wget is missing! trying to install it... \e[0m"
		result=$(eval "$PACK_MGR wget $PACK_MGR_OPTIONS" 2>&1)
		result_code=$?
		if [[ $result_code -eq 0 ]]; then
			WGET=$(which wget 2>/dev/null)
		else
			echo "Cannot 'wget'. Please install 'wget'  manually."
			return 1
		fi
	fi
	for program in "${programs_missing[@]}"; do
		if [[ "$program" == "calibre" ]]; then		
			# TODO: check if ebook-convert installed with the right min version
			if command -v $program >/dev/null 2>&1; then
				echo -e "\e[32m===============>>> Calibre is installed! <<===============\e[0m"
			else
				# avoid conflict with calibre builtin lxml
				python3 -m pip uninstall lxml -y 2>/dev/null
				echo -e "\e[33mInstalling Calibre...\e[0m"
				if [[ "$OSTYPE" == darwin* ]]; then
					eval "$PACK_MGR --cask calibre"
				else
					if [[ "$SUDO" == "sudo" ]]; then
						$SUDO -v && $WGET -nv -O- $CALIBRE_INSTALLER_URL | $SUDO sh /dev/stdin
					else
						$WGET -nv -O- $CALIBRE_INSTALLER_URL | sh /dev/stdin
					fi
				fi
				eval "$SUDO $PACK_MGR $program $PACK_MGR_OPTIONS"				
				if command -v $program >/dev/null 2>&1; then
					echo -e "\e[32m===============>>> $program is installed! <<===============\e[0m"
				else
					echo -e "\e[31m===============>>> $program installation failed.\e[0m"
				fi
			fi	
		elif [[ "$program" == "rust" || "$program" == "rustc" ]]; then
			curl --proto '=https' --tlsv1.2 -sSf $RUST_INSTALLER_URL | sh -s -- -y
			source $HOME/.cargo/env
			if command -v $program &>/dev/null; then
				echo -e "\e[32m===============>>> $program is installed! <<===============\e[0m"
			else
				echo -e "\e[31m===============>>> $program installation failed.\e[0m"
			fi
		elif [[ "$program" == "tesseract" || "$program" == "tesseract-ocr" ]]; then
			eval "$SUDO $PACK_MGR $program $PACK_MGR_OPTIONS"
			if command -v $program >/dev/null 2>&1; then
				echo -e "\e[32m===============>>> $program is installed! <<===============\e[0m"
				case "$OS_LANG" in
					en) tess_lang="eng" ;;
					fr) tess_lang="fra" ;;
					de) tess_lang="deu" ;;
					it) tess_lang="ita" ;;
					es) tess_lang="spa" ;;
					pt) tess_lang="por" ;;
					ar) tess_lang="ara" ;;
					tr) tess_lang="tur" ;;
					ru) tess_lang="rus" ;;
					bn) tess_lang="ben" ;;
					zh) tess_lang="chi_sim" ;;
					fa) tess_lang="fas" ;;
					hi) tess_lang="hin" ;;
					hu) tess_lang="hun" ;;
					id) tess_lang="ind" ;;
					jv) tess_lang="jav" ;;
					ja) tess_lang="jpn" ;;
					ko) tess_lang="kor" ;;
					pl) tess_lang="pol" ;;
					ta) tess_lang="tam" ;;
					te) tess_lang="tel" ;;
					yo) tess_lang="yor" ;;
					*) tess_lang="eng" ;;
				esac
				tess_lang="$(get_iso3_lang $OS_LANG)"
				echo "Detected system language: $OS_LANG → installing Tesseract OCR language: $tess_lang"
				langpack=""
				if command -v brew &> /dev/null; then
					langpack="tesseract-lang-$tess_lang"
				elif command -v apt-get &>/dev/null; then
					langpack="tesseract-ocr-$tess_lang"
				elif command -v dnf &>/dev/null || command -v yum &>/dev/null; then
					langpack="tesseract-langpack-$tess_lang"
				elif command -v zypper &>/dev/null; then
					langpack="tesseract-ocr-$tess_lang"
				elif command -v pacman &>/dev/null; then
					langpack="tesseract-data-$tess_lang"
				elif command -v apk &>/dev/null; then
					langpack="tesseract-ocr-$tess_lang"
				else
					echo "Cannot recognize your applications package manager. Please install the required applications manually."
					return 1
				fi
				if [[ -n "$langpack" ]]; then
					eval "$SUDO $PACK_MGR $langpack $PACK_MGR_OPTIONS"
					if tesseract --list-langs | grep -q "$tess_lang"; then
						echo "Tesseract OCR language '$tess_lang' successfully installed."
					else
						echo "Tesseract OCR language '$tess_lang' not installed properly."
					fi
				fi
			else
				echo -e "\e[31m===============>>> $program installation failed.\e[0m"
			fi
		else
			eval "$SUDO $PACK_MGR $program $PACK_MGR_OPTIONS"
			if command -v $program >/dev/null 2>&1; then
				echo -e "\e[32m===============>>> $program is installed! <<===============\e[0m"
			else
				echo -e "\e[31m===============>>> $program installation failed.\e[0m"
			fi
		fi
	done
	if check_required_programs "${HOST_PROGRAMS[@]}"; then
		return 0
	else
		echo "Some programs didn't install successfuly, please report the log to the support"
	fi
}

function check_conda {

	function compare_versions {
		local ver1=$1
		local ver2=$2
		# Pad each version to 3 parts
		IFS='.' read -r v1_major v1_minor <<<"$ver1"
		IFS='.' read -r v2_major v2_minor <<<"$ver2"

		((v1_major < v2_major)) && return 1
		((v1_major > v2_major)) && return 2
		((v1_minor < v2_minor)) && return 1
		((v1_minor > v2_minor)) && return 2
		return 0
	}

	if ! command -v conda &> /dev/null || [[ ! -f "$CONDA_ENV" ]]; then
		local installer_url
		local installer_path="/tmp/Miniforge3.sh"
		local config_path
		echo -e "\e[33mDownloading Miniforge3 installer...\e[0m"
		if [[ "$OSTYPE" == darwin* ]]; then
			config_path="$HOME/.zshrc"
			curl -fsSLo "$installer_path" "$MINIFORGE_MACOSX_INSTALLER_URL"
		else
			config_path="$HOME/.bashrc"
			wget -O "$installer_path" "$MINIFORGE_LINUX_INSTALLER_URL"
		fi
		if [[ -f "$installer_path" ]]; then
			echo -e "\e[33mInstalling Miniforge3...\e[0m"
			bash "$installer_path" -b -u -p "$CONDA_HOME"
			rm -f "$installer_path"
			if [[ -f "$CONDA_HOME/bin/conda" ]]; then
				if [[ ! -f "$HOME/.condarc" ]]; then
					$CONDA_HOME/bin/conda config --set auto_activate false
				fi
				[[ -f "$config_path" ]] || touch "$config_path"
				grep -qxF 'export PATH="$HOME/Miniforge3/bin:$PATH"' "$config_path" || echo 'export PATH="$HOME/Miniforge3/bin:$PATH"' >> "$config_path"
				source "$config_path"
				echo -e "\e[32m===============>>> Miniforge3 is installed! <<===============\e[0m"
					if ! grep -iqFx "Miniforge3" "$INSTALLED_LOG"; then
						echo "Miniforge3" >> "$INSTALLED_LOG"
					fi
			else
				echo -e "\e[31m===============>>> Miniforge3 installation failed.\e[0m"
				return 1
			fi
		else
			echo -e "\e[31m===============>>> Miniforge3 installer not found!.\e[0m"
			return 1
		fi
	fi
	if [[ ! -d "$SCRIPT_DIR/$PYTHON_ENV" ]]; then
		if [[ "$OSTYPE" == darwin* && "$ARCH" == "x86_64" ]]; then
			PYTHON_VERSION="3.11"
		elif [[ -r /proc/device-tree/model ]]; then
			# Detect Jetson and select correct Python version
			MODEL=$(tr -d '\0' </proc/device-tree/model | tr 'A-Z' 'a-z')
			if [[ "$MODEL" == *jetson* ]]; then
				PYTHON_VERSION="3.10"
			fi
		else
			compare_versions "$PYTHON_VERSION" "$MIN_PYTHON_VERSION"
			case $? in
				1) PYTHON_VERSION="$MIN_PYTHON_VERSION" ;;
			esac
			compare_versions "$PYTHON_VERSION" "$MAX_PYTHON_VERSION"
			case $? in
				2) PYTHON_VERSION="$MAX_PYTHON_VERSION" ;;
			esac
		fi
		echo -e "\e[33mCreating ./python_env version $PYTHON_VERSION...\e[0m"
		chmod -R u+rwX,go+rX "$SCRIPT_DIR/audiobooks" "$SCRIPT_DIR/tmp" "$SCRIPT_DIR/models"
		conda update --all -y
		conda clean --index-cache -y
		conda clean --packages --tarballs -y
		conda create --prefix "$SCRIPT_DIR/$PYTHON_ENV" python=$PYTHON_VERSION -y || return 1
		source "$CONDA_ENV" || return 1
		conda activate "$SCRIPT_DIR/$PYTHON_ENV" || return 1
		install_python_packages || return 1
		conda deactivate > /dev/null 2>&1
		conda deactivate > /dev/null 2>&1
	fi
	return 0
}

function check_docker {
	if ! command -v docker &> /dev/null; then
		echo -e "\e[31m===============>>> Docker is not installed or not running. Please install or run Docker manually.\e[0m"
		return 1
	fi
	return 0
}

function install_python_packages {
	echo "[ebook2audiobook] Installing dependencies..."
	python3 -m pip cache purge > /dev/null 2>&1
	python3 -m pip install --upgrade pip > /dev/null 2>&1
	python3 -m pip install --upgrade --no-cache-dir --progress-bar on --disable-pip-version-check --use-pep517 -r "$SCRIPT_DIR/requirements.txt" || exit 1
	torch_ver=$(pip show torch 2>/dev/null | awk '/^Version:/{print $2}')
	if [[ "$(printf '%s\n%s\n' "$torch_ver" "2.2.2" | sort -V | head -n1)" == "$torch_ver" ]]; then
		python3 -m pip install --upgrade --no-cache-dir --use-pep517 "numpy<2" || exit 1
	fi
	python3 -m unidic download || exit 1
	echo "[ebook2audiobook] Installation completed."
	return 0
}

function check_device_info {
arg="$1"
python3 - << EOF
from lib.classes.device_installer import DeviceInstaller
device = DeviceInstaller()
result = device.check_device_info("$arg")
if result:
	print(result)
	raise SystemExit(0)
raise SystemExit(1)
EOF
}

function install_device_packages {
arg="$1"
python3 - << EOF
import sys
from lib.classes.device_installer import DeviceInstaller
device = DeviceInstaller()
exit_code = device.install_device_packages('''$arg''')
sys.exit(exit_code)
EOF
}

function check_sitecustomized {
	local src_pyfile="$SCRIPT_DIR/components/sitecustomize.py"
	local site_packages_path=$(python3 -c "import sysconfig;print(sysconfig.get_paths()['purelib'])")
	local dst_pyfile="$site_packages_path/sitecustomize.py"
	if [ ! -f "$dst_pyfile" ] || [ "$src_pyfile" -nt "$dst_pyfile" ]; then
		if cp -p "$src_pyfile" "$dst_pyfile"; then
			echo "Installed sitecustomize.py hook in $dst_pyfile"
		else
			echo -e "\e[31m===============>>> sitecustomize.py hook installation error: copy failed.\e[0m" >&2
			exit 1
		fi
	fi
	return 0
}

function build_docker_image {
	local ARG="$1"
	local TAG=$(python3 -c 'import json,sys; print(json.loads(sys.argv[1])["tag"])' "$ARG")
	if ! command -v docker >/dev/null 2>&1; then
		echo -e "\e[31m===============>>> Error: Docker must be installed and running!.\e[0m"
		return 1
	fi
	DOCKER_IMG_NAME="${DOCKER_IMG_NAME}:${TAG}"
	if docker compose version >/dev/null 2>&1; then
		BUILD_NAME="$DOCKER_IMG_NAME" docker compose \
			--progress plain \
			build \
			--no-cache \
			--build-arg DOCKER_DEVICE_STR="$ARG" \
			--build-arg DOCKER_PROGRAMS_STR="${DOCKER_PROGRAMS[*]}" \
			--build-arg CALIBRE_INSTALLER_URL="$CALIBRE_INSTALLER_URL" \
			--build-arg ISO3_LANG="$ISO3_LANG" \
			|| return 1
	else
		docker build \
			--no-cache \
			--progress plain \
			--build-arg DOCKER_DEVICE_STR="$ARG" \
			--build-arg DOCKER_PROGRAMS_STR="${DOCKER_PROGRAMS[*]}" \
			--build-arg CALIBRE_INSTALLER_URL="$CALIBRE_INSTALLER_URL" \
			--build-arg ISO3_LANG="$ISO3_LANG" \
			-t "$DOCKER_IMG_NAME" \
			. || return 1
	fi
}

########################################

if [[ -n "${arguments[help]+exists}" && ${arguments[help]} == true ]]; then
	python "$SCRIPT_DIR/app.py" "${ARGS[@]}"
else
	if [[ "$SCRIPT_MODE" == "$BUILD_DOCKER" ]]; then
		if [[ "$DOCKER_DEVICE_STR" == "" ]]; then
			check_docker || exit 1
			if docker image inspect "$DOCKER_IMG_NAME" >/dev/null 2>&1; then
				echo "[STOP] Docker image '$DOCKER_IMG_NAME' already exists. Aborting build."
				echo "Delete it using: docker rmi $DOCKER_IMG_NAME"
				exit 1
			fi
			build_docker_image "$(check_device_info "${SCRIPT_MODE}")" || exit 1
			echo "Docker image ready! to run your docker: docker run -it --rm -p 7860:7860 $DOCKER_IMG_NAME [--options]"
		elif [[ "$DOCKER_DEVICE_STR" != "" ]];then
			install_python_packages || exit 1
			install_device_packages "${DOCKER_DEVICE_STR}" || exit 1
			check_sitecustomized || exit 1
		fi
	elif [[ "$SCRIPT_MODE" == "$NATIVE" ]]; then
		# Check if running in a Conda or Python virtual environment
		if [[ -n "$CONDA_DEFAULT_ENV" ]]; then
			current_pyvenv="$CONDA_PREFIX"
		elif [[ -n "$VIRTUAL_ENV" ]]; then
			current_pyvenv="$VIRTUAL_ENV"
		fi
		# If neither environment variable is set, check Python path
		if [[ -z "$current_pyvenv" ]]; then
			PYTHON_PATH=$(which python 2>/dev/null)
			if [[ ( -n "$CONDA_PREFIX" && "$PYTHON_PATH" == "$CONDA_PREFIX/bin/python" ) || ( -n "$VIRTUAL_ENV" && "$PYTHON_PATH" == "$VIRTUAL_ENV/bin/python" ) ]]; then
				current_pyvenv="${CONDA_PREFIX:-$VIRTUAL_ENV}"
			fi
		fi
		# Output result if a virtual environment is detected
		if [[ -n "$current_pyvenv" ]]; then
			echo -e "\e[31m===============>>> Error: Current python virtual environment detected: $current_pyvenv..\e[0m"
			echo -e "This script runs with its own virtual env and must be out of any other virtual environment when it's launched."
			echo -e "If you are using conda then you would type in:"
			echo -e "conda deactivate"
			exit 1
		fi
		check_required_programs "${HOST_PROGRAMS[@]}" || install_programs || exit 1
		check_conda || { echo -e "\e[31m===============>>> check_conda() failed.\e[0m"; exit 1; }
		source "$CONDA_ENV" || exit 1
		conda activate "$SCRIPT_DIR/$PYTHON_ENV" || { echo -e "\e[31m===============>>> conda activate failed.\e[0m"; exit 1; }
		check_sitecustomized || exit 1
		check_desktop_app || exit 1
		python "$SCRIPT_DIR/app.py" --script_mode "$SCRIPT_MODE" "${ARGS[@]}" || exit 1
		conda deactivate > /dev/null 2>&1
		conda deactivate > /dev/null 2>&1
	else
		echo -e "\e[31m===============>>> ebook2audiobook is not correctly installed.\e[0m"
	fi
fi

exit 0